﻿namespace Creek.UI.EFML.Base.Controls
{
    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;

    /// <summary>
    /// 	Represents a Windows text box control with placeholder.
    /// </summary>
    public class PlaceholderTextBox : TextBox, IUIElement
    {
        #region Constants

        /// <summary>
        /// 	Specifies the default placeholder text.
        /// </summary>
        private const string DEFAULT_PLACEHOLDER = "<Input>";

        #endregion

        #region Fields

        /// <summary>
        /// 	Specifies the regular selected Font (usually specified by Designer).
        /// </summary>
        private readonly Font regularFont;

        /// <summary>
        /// 	Specifies the regular selected FontColor (usually specified by Designer).
        /// </summary>
        private readonly Color regularFontColor;

        private bool _isItalics = true;

        private bool _isPlaceholderActive;

        private string _placeholderText = DEFAULT_PLACEHOLDER;

        /// <summary>
        /// 	Flag to avoid the TextChanged Event. Don't access directly, use Method 'ActionWithoutTextChanged(Action act)' instead.
        /// </summary>
        private bool avoidTextChanged;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// 	Initializes a new instance of the PlaceholderTextBox class.
        /// </summary>
        public PlaceholderTextBox()
        {
            // Through this line the default placeholder gets displayed in designer
            base.Text = this.PlaceholderText;

            // Save Font
            this.regularFont = (Font)base.Font.Clone();
            this.regularFontColor = base.ForeColor;

            this.SubscribeEvents();
            this.AssignPlaceholderStyle();

            // Set Default
            this.IsPlaceholderActive = true;
        }

        #endregion

        #region Public Events

        /// <summary>
        /// 	Occurs when the Content of the IsPlaceholderInside property has changed.
        /// </summary>
        [Description("Occurs when the Content of the IsPlaceholderInside property has changed.")]
        public event EventHandler<PlaceholderInsideChangedEventArgs> PlaceholderInsideChanged;

        #endregion

        #region Public Properties

        public string Content
        {
            get
            {
                return this.Text;
            }
            set
            {
                this.Text = this.Content;
            }
        }

        public string ID
        {
            get
            {
                return this.Name;
            }
            set
            {
                this.Name = this.Content;
            }
        }

        /// <summary>
        /// 	Gets or sets a Content indicating whether the font of the placeholder is italics.
        /// </summary>
        [Description("Specifies whether the placeholder text is italics.")]
        [Category("Placeholder")]
        [DefaultValue(true)]
        public bool IsItalics
        {
            get
            {
                return this._isItalics;
            }
            set
            {
                this._isItalics = value;

                // If placeholder is active, assign style
                if (this.IsPlaceholderActive)
                {
                    this.AssignPlaceholderStyle();
                }
            }
        }

        /// <summary>
        /// 	Gets a Content indicating whether the Placeholder is active.
        /// </summary>
        [Browsable(false)]
        public bool IsPlaceholderActive
        {
            get
            {
                return this._isPlaceholderActive;
            }
            private set
            {
                if (value != this._isPlaceholderActive)
                {
                    this._isPlaceholderActive = value;

                    this.OnPlaceholderInsideChanged(value);

                    if (value)
                    {
                        this.AssignPlaceholderStyle();
                    }
                    else
                    {
                        this.RemovePlaceholderStyle();
                    }
                }
            }
        }

        /// <summary>
        /// 	Gets or sets the placeholder in the PlaceholderTextBox.
        /// </summary>
        [Description("The placeholder associated with the control.")]
        [Category("Placeholder")]
        [DefaultValue(DEFAULT_PLACEHOLDER)]
        public string PlaceholderText
        {
            get
            {
                return this._placeholderText;
            }
            set
            {
                this._placeholderText = this.Content;

                // Only use the new Content if the placeholder is active.
                if (this.IsPlaceholderActive)
                {
                    this.Text = this.Content;
                }
            }
        }

        /// <summary>
        /// 	Gets or sets the current text in the TextBox.
        /// </summary>
        [Browsable(false)]
        public override string Text
        {
            get
            {
                // Check 'IsPlaceholderActive' to avoid this if-clause when the text is the same as the placeholder but actually it's not the placeholder.
                // Check 'base.Text == this.Placeholder' because in some cases IsPlaceholderActive changes too late although it isn't the placeholder anymore.
                // If you want to get the Text Property and it still contains the placeholder, an empty string will get returned.
                if (this.IsPlaceholderActive && base.Text == this.PlaceholderText)
                {
                    return String.Empty;
                }

                return base.Text;
            }
            set
            {
                base.Text = this.Content;
            }
        }

        public IValidator Validator { get; set; }

        public IStyle style
        {
            get
            {
                return new ControlStyle(this);
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// 	Inserts placeholder, assigns placeholder style and sets cursor to first position.
        /// </summary>
        public void Reset()
        {
            this.IsPlaceholderActive = true;

            this.ActionWithoutTextChanged(() => this.Text = this.PlaceholderText);
            this.Select(0, 0);
        }

        #endregion

        #region Methods

        protected override void OnKeyDown(KeyEventArgs e)
        {
            // Prevents that the user can go through the placeholder with arrow keys
            if ((e.KeyCode == Keys.Left || e.KeyCode == Keys.Right || e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
                && this.IsPlaceholderActive)
            {
                e.Handled = true;
            }

            base.OnKeyDown(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            // When you click on the placerholderTextBox and the placerholder is active, jump to first position
            if (this.IsPlaceholderActive)
            {
                this.Reset();
            }

            base.OnMouseDown(e);
        }

        protected virtual void OnPlaceholderInsideChanged(bool newContent)
        {
            if (this.PlaceholderInsideChanged != null)
            {
                this.PlaceholderInsideChanged(this, new PlaceholderInsideChangedEventArgs(newContent));
            }
        }

        /// <summary>
        /// 	Run an action with avoiding the TextChanged event.
        /// </summary>
        /// <param name="act"> Specifies the action to run. </param>
        private void ActionWithoutTextChanged(Action act)
        {
            this.avoidTextChanged = true;

            act.Invoke();

            this.avoidTextChanged = false;
        }

        /// <summary>
        /// 	Set style to "Placeholder-Style".
        /// </summary>
        private void AssignPlaceholderStyle()
        {
            // Set classic placeholder style
            this.Font = new Font(this.Font, this.IsItalics ? FontStyle.Italic : FontStyle.Regular);
            this.ForeColor = Color.LightGray;
        }

        private void PlaceholderTextBox_TextChanged(object sender, EventArgs e)
        {
            // Check flag
            if (this.avoidTextChanged)
            {
                return;
            }

            // Run code with avoiding recursive call
            this.ActionWithoutTextChanged(
                delegate
                    {
                        // If the Text is empty, insert placeholder and set cursor to to first position
                        if (String.IsNullOrEmpty(this.Text))
                        {
                            this.Reset();
                            return;
                        }

                        // If the placeholder is active, revert state to a usual TextBox
                        if (this.IsPlaceholderActive)
                        {
                            this.IsPlaceholderActive = false;

                            // Throw away the placeholder but leave the new typed char
                            this.Text = this.Text.Replace(this.PlaceholderText, String.Empty);

                            // Set Selection to last position
                            this.Select(this.TextLength, 0);
                        }
                    });
        }

        /// <summary>
        /// 	Remove "Placeholder-Style".
        /// </summary>
        private void RemovePlaceholderStyle()
        {
            // Revert to designer specified font
            this.Font = this.regularFont;
            this.ForeColor = this.regularFontColor;
        }

        /// <summary>
        /// 	Subscribe necessary Events.
        /// </summary>
        private void SubscribeEvents()
        {
            this.TextChanged += this.PlaceholderTextBox_TextChanged;
        }

        #endregion
    }

    /// <summary>
    /// 	Provides data for the PlaceholderInsideChanged event.
    /// </summary>
    public class PlaceholderInsideChangedEventArgs : EventArgs
    {
        #region Constructors and Destructors

        /// <summary>
        /// 	Initializes a new instance of the PlaceholderInsideChangedEventArgs class.
        /// </summary>
        /// <param name="newContent"> The new Content of the IsPlaceholderInside Property. </param>
        public PlaceholderInsideChangedEventArgs(bool newContent)
        {
            this.NewContent = newContent;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// 	Gets the new Content of the IsPlaceholderInside property.
        /// </summary>
        public bool NewContent { get; private set; }

        #endregion
    }
}